# package deb

# import (
# 	"fmt"
# 	"sort"
# 	"strings"

# 	"github.com/aptly-dev/aptly/aptly"
# 	"github.com/aptly-dev/aptly/utils"
# )
# 
from typing import List,Dict,Tuple
from enum import Enum
# from deb.reflist import *
from deb.package_collection import  *
# from deb.listp import *
from deb.package import *
from deb.version import *
from utils.listup import *
from utils.patch import *
# // Dependency options
class DepFollow(Enum):
# const (
# 	// DepFollowSource pulls source packages when required
    DepFollowSource = 1 << 0
# 	// DepFollowSuggests pulls from suggests
    DepFollowSuggests=2
# 	// DepFollowRecommends pulls from recommends
    DepFollowRecommends=3
# 	// DepFollowAllVariants follows all variants if depends on "a | b"
    DepFollowAllVariants=4
# 	// DepFollowBuild pulls build dependencies
    DepFollowBuild=5
# 	// DepVerboseResolve emits additional logs while dependencies are being resolved
    DepVerboseResolve=6
# )

# // PackageList is list of unique (by key) packages
# //
# // It could be seen as repo snapshot, repo contents, result of filtering,
# // merge, etc.
# //
# // If indexed, PackageList starts supporting searching
class PackageList :
    # from .package import *
    # // Straight list of packages as map
    packages ={}
    # // Indexed list of packages, sorted by name internally
    packagesIndex =[]
    # // Map of packages for each virtual package (provides)
    providesIndex ={}
    # // Package key generation deftion
    keydef =None
    # // Allow duplicates?
    duplicatesAllowed :bool=None
    # // Has index been prepared?
    indexed :bool=None
    # // ForEach calls handler for each package in list
    def  ForEach(l,handler ) :
        for  p in l.packages.keys() :
             handler(p)
    # // Add appends package to package list, additionally checking for uniqueness
    def  Add(l,p :Package) :
        key = l.keydef(p)
        existing= l.packages.get(key)
        if existing is not None:
            if not  existing.Equals(p) :
                raise Exception("conflict in package {}".format( p))
        
        
        
        l.packages[key] = p

        if l.indexed :
            for  provides in  p.Provides :
                l.providesIndex[provides].append( p)
            i=len(l.packagesIndex)
            for j in range(i):
                if  l.lessPackages(p, l.packagesIndex[j]):
                    i=j

            # // insert p into l.packagesIndex in position i
            l.packagesIndex .append( None)

            # copy(l.packagesIndex[i+1:], l.packagesIndex[i:])
            # 用数组的insert
            l.packagesIndex.index(i, p)
        
    def  lessPackages(l,iPkg, jPkg :Package) ->bool :
        if iPkg.Name == jPkg.Name :
            cmp = CompareVersions(iPkg.Version, jPkg.Version)
            if cmp == 0 :
                return iPkg.Architecture < jPkg.Architecture
            return cmp == 1
        return iPkg.Name < jPkg.Name
    
    # // Filter filters package index by specified queries (ORed together), possibly pulling dependencies
    def  Filter(l ,queries , withDependencies :bool, source , dependencyOptions :int, architecturesList :List[str]) :
        return l.FilterWithProgress(queries, withDependencies, source, dependencyOptions, architecturesList, None)
    
# // FilterWithProgress filters package index by specified queries (ORed together), possibly pulling dependencies and displays progress
    def  FilterWithProgress(l,queries :list, withDependencies :bool, source, dependencyOptions: int, architecturesList :List[str], progress ):
        if not l.indexed :
            raise Exception("list not indexed, can't filter")
        

        result = NewPackageList()

        for query in queries :
            result.Append(query.Query(l))
        

        if withDependencies :
            added = result.Len()
            result.PrepareIndex()

            dependencySource = NewPackageList()
            if source is not None  :
                dependencySource.Append(source)
            
            dependencySource.Append(result)
            dependencySource.PrepareIndex()

            # // while some new dependencies were discovered
            while added > 0 :
                added = 0

                # // find missing dependencies
                missing, err = result.VerifyDependencies(dependencyOptions, architecturesList, dependencySource, progress)
                if err is not None  :
                    return None, err
                

                # // try to satisfy dependencies
                for dep in missing :
                    if dependencyOptions&DepFollow.DepFollowAllVariants == 0 :
                        # // dependency might have already been satisfied
                        # // with packages already been added
                        # //
                        # // when follow-all-variants is enabled, we need to try to expand anyway,
                        # // as even if dependency is satisfied now, there might be other ways to satisfy dependency
                        if result.Search(dep, False) is not None  :
                            if dependencyOptions&DepFollow.DepVerboseResolve == DepFollow.DepVerboseResolve and progress is not None  :
                                print("@{y}Already satisfied dependency@|: %s with %s",  result.Search(dep, True))
                            
                            continue
                        
                    

                    searchResults = l.Search(dep, True)
                    if len(searchResults) > 0 :
                        for p in searchResults :
                            if result.Has(p) :
                                continue
                            

                            if dependencyOptions&DepFollow.DepVerboseResolve == DepFollow.DepVerboseResolve and progress is not None  :
                                print("@{g}Injecting package@|: %s", p)
                            
                            result.Add(p)
                            dependencySource.Add(p)
                            added+=1
                            if dependencyOptions&DepFollow.DepFollowAllVariants == 0 :
                                break
                            
                        
                    else :
                        if dependencyOptions&DepFollow.DepVerboseResolve == DepFollow.DepVerboseResolve and progress is not None  :
                            print("@{r}Unsatisfied dependency@|: %s", dep.String())
                        

        return result, None
    

    # // VerifyDependencies looks for missing dependencies in package list.
    # //
    # // Analysis would be performed for each architecture, in specified sources
    def  VerifyDependencies(l,options :int, architectures :List[str], sources , progress ) :
        l.PrepareIndex()
        missing = []#make([]Dependency, 0, 128)

        if progress is not None  :
            print((l.Len())*(len(architectures)), False)
        

        for  arch in architectures :
            cache =[]# make(map[string]bool, 2048)

            for p in l.packagesIndex :
                if progress is not None  :
                    print(1)
                

                if not p.MatchesArchitecture(arch) :
                    continue
                

                for dep in p.GetDependencies(options) :
                    variants, err = ParseDependencyVariants(dep)
                    if err is not None  :
                        return (None, "unable to process package {}: {}".format(p, err))
                    

                    variants = depSliceDeduplicate(variants)

                    variantsMissing =[]# make([]Dependency, 0, len(variants))

                    for   dep in variants :
                        if dep.Architecture == "" :
                            dep.Architecture = arch
                        

                        hash = dep.Hash()
                        satisfied, ok = cache[hash]
                        if not ok :
                            satisfied = sources.Search(dep, False) is not None 
                            cache[hash] = satisfied
                        

                        if not satisfied and not ok :
                            variantsMissing .append( dep)
                        

                        if satisfied and options&DepFollow.DepFollowAllVariants == 0 :
                            variantsMissing = None
                            break
                    missing =missing+ variantsMissing
         

        if progress is not None  :
            print('progress.ShutdownBar()')
        

        if options&DepFollow.DepVerboseResolve == DepFollow.DepVerboseResolve and progress is not None  :
            missingStr =[]# make([]string, len(missing))
            for i in missing :
                missingStr.append( missing[i].String())
            
            print("@{y}Missing dependencies:@| %s", ''.join(missingStr, ", "))
        

        return (missing, None)
    

    # // PrepareIndex prepares list for indexing
    def  PrepareIndex(l) :
        if l.indexed :
            return
        

        l.packagesIndex =[]# make([]*Package, l.Len())
        l.providesIndex ={}# make(map[string][]*Package, 128)

        i = 0
        for  _,p in l.packages.items() :
            l.packagesIndex.append( p)
            i+=1
            print(p.Provides)
            for   provides in p.Provides :
                l.providesIndex[provides].append( p)
            
        

        # sort.Sort(l)
        # print()
        # l.sort()
        l.indexed = True
    


    # // Has checks whether package is already in the list
    def Has(l,p :Package) ->bool :
        key = l.keydef(p)
        ok = l.packages[key]

        return ok
    





    # // ForEachIndexed calls handler for each package in list in indexed order
    def  ForEachIndexed(l,handler ) :
        if not l.indexed :
            raise Exception("list not indexed, can't iterate")
        

        
        for  p in l.packagesIndex :
            err = handler(p)
            if err is not None  :
                return err
            
        
        return err
    

    # // Len returns number of packages in the list
    def  Len(l) ->int :
        return len(l.packages)
    
    # // Append adds content from one package list to another
    def   Append(l,pl) :
        if l.indexed :
            raise Exception("Append not supported when indexed")
        for k, p in pl.packages :
            existing, ok = l.packages[k]
            if ok :
                if not existing.Equals(p) :
                    return "conflict in package {}".format( p)
                
            else :
                l.packages[k] = p
            

        return None
    

    # // Remove removes package from the list, and updates index when required
    def   Remove(l,p :Package) :
        del l.packages[ l.keydef(p)]
        def NoName(j :int):
            return l.packagesIndex[j].Name >= p.Name
        if l.indexed :
            for _, provides in p.Provides :
                for i, pkg in l.providesIndex[provides] :
                    if pkg.Equals(p) :
                        # // remove l.ProvidesIndex[provides][i] w/o preserving order
                        l.providesIndex[provides][len(l.providesIndex[provides])-1], l.providesIndex[provides][i], l.providesIndex[provides] =\
                            None, l.providesIndex[provides][len(l.providesIndex[provides])-1], l.providesIndex[provides][:len(l.providesIndex[provides])-1]
                        break
                    
                
            

            i = sortSearch(len(l.packagesIndex), NoName)
            while i < len(l.packagesIndex) and l.packagesIndex[i].Name == p.Name :
                if l.packagesIndex[i].Equals(p) :
                    # TODO 此处有问题，暂时懒得搞了
                    # // remove l.packagesIndex[i] preserving order
                    copy(l.packagesIndex[i:], l.packagesIndex[i+1:])
                    l.packagesIndex[len(l.packagesIndex)-1] = None
                    l.packagesIndex = l.packagesIndex[:len(l.packagesIndex)-1]
                    break
                
                i+=1
            
        
    

    # // Architectures returns list of architectures present in packages and flag if source packages are present.
    # //
    # // If includeSource is True, meta-architecture "source" would be present in the list
    def  Architectures(l,includeSource :bool) ->List[str]:
        result =[]# make([]string, 0, 10)
        for   pkg in l.packages :
            if pkg.Architecture != ArchitectureAll and (pkg.Architecture != ArchitectureSource or includeSource) and not utils.StrSliceHasItem(result, pkg.Architecture) :
                result . append( pkg.Architecture)
            
        return result
    

    # // Strings builds list of strings with package keys
    def  Strings(l) ->List[str] :
        result = []#make([]string, l.Len())
        i = 0
        for  p in l.packages :
            
            result.append(l.packages[p].Key(""))
            i+=1
        return result
    


    # // Swap swaps two packages in index
    def  Swap(l,i, j :int) :
        l.packagesIndex[i], l.packagesIndex[j] = l.packagesIndex[j], l.packagesIndex[i]
    



    # // Less compares two packages by name (lexographical) and version (latest to oldest)
    def   Less(l,i, j :int) ->bool :
        return l.lessPackages(l.packagesIndex[i], l.packagesIndex[j])
    


    # // Scan searches package index using full scan
    def  Scan(l,q ) :
        result = NewPackageListWithDuplicates(l.duplicatesAllowed, 0)
        for   pkg in l.packages :
            if q.Matches(pkg) :
                result.Add(pkg)
             

        return
    

    # // SearchSupported returns True for PackageList
    def SearchSupported(l) ->bool :
        return True
    

    # // SearchByKey looks up package by exact key reference
    def  SearchByKey(l,arch, name, version :str):
        result = NewPackageListWithDuplicates(l.duplicatesAllowed, 0)

        pkg = l.packages.get(["P"+arch+" "+name+" "+version])
        if pkg is not None:
            result.Add(pkg)
        

        return result
    

    # // Search searches package index for specified package(s) using optimized queries
    def  Search(l,dep :Dependency, allMatches :bool) ->List[Package] :
        searchResults=[]
        if not l.indexed :
            raise Exception("list not indexed, can't search")
        
        def NoName(j:int):
            return l.packagesIndex[j].Name >= dep.Pkg
        i = sortSearch(len(l.packagesIndex), NoName)

        while i < len(l.packagesIndex) and l.packagesIndex[i].Name == dep.Pkg :
            p = l.packagesIndex[i]
            if p.MatchesDependency(dep) :
                searchResults . append( p)

                if not allMatches :
                    break
                
            

            i+=1
        

        if dep.Relation == VersionRelation.VersionDontCare :
            for  p in l.providesIndex[dep.Pkg] :
                if dep.Architecture == "" or p.MatchesArchitecture(dep.Architecture) :
                    searchResults.append( p)

                    if not allMatches :
                        break 

        return searchResults
    
# // PackageConflictError means that package can't be added to the list due to error
# type PackageConflictError struct {
# 	error
# }

# // Verify interface
# var (
# 	_ sort.Interface = &PackageList{}
# 	_ PackageCatalog = &PackageList{}
# )



def packageShortKey(p :Package) ->str :
    return p.ShortKey("")


def packageFullKey(p :Package) ->str :
    return p.Key("")


# // NewPackageList creates empty package list without duplicate package
def NewPackageList() ->PackageList :
    return NewPackageListWithDuplicates(False, 1000)


# // NewPackageListWithDuplicates creates empty package list which might allow or block duplicate packages
def NewPackageListWithDuplicates(duplicates :bool, capacity :int) ->PackageList :
    if capacity == 0 :
        capacity = 1000
    

    result = PackageList()
    result.packages= {} #  make(map[string]*Package, capacity)
    result.duplicatesAllowed= duplicates
    result.keydef= packageShortKey
    

    if duplicates :
        result.keydef = packageFullKey
    

    return result


# // NewPackageListFromRefList loads packages list from PackageRefList
def NewPackageListFromRefList(reflist , collection :PackageCollection, progress ) ->Tuple[PackageList,str]:
    # // empty reflist
    if reflist.Len()==0 :
        return NewPackageList(),None

    result = NewPackageListWithDuplicates(False, reflist.Len())


    print('Info:refList_Len',reflist.Len())
    def NoName(key):
        p= collection.ByKey(key)
        if p is None  :
            raise Exception("unable to load package with key {}:".format(key))
        return result.Add(p)
    reflist.ForEach(NoName)
    return result,None


# // depSliceDeduplicate removes dups in slice of Dependencies
def depSliceDeduplicate(s :List[Dependency]) ->List[Dependency] :
    l = len(s)
    if l < 2 :
        return s
    
    if l == 2 :
        if s[0] == s[1] :
            return s[0:1]
        
        return s
    

    found ={}# make(map[string]bool, l)
    j = 0
    
    for x in s :
        h = s[x].Hash()
        if not found[h] :
            found[h] = True
            s[j] = s[i]
            j+=1
        i+=1
    

    return s[:j]
